cmake_minimum_required(VERSION 3.10)
include(ProcessorCount)

project(Alive2)

if (NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Debug)
endif()

message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON)
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)

if (MSVC)
  set(CMAKE_CXX_FLAGS                "/GL /EHsc /W2 /Zc:__cplusplus ${CMAKE_CXX_FLAGS}")
  set(CMAKE_CXX_FLAGS_DEBUG          "/Od /Zi ${CMAKE_CXX_FLAGS_DEBUG}")
  set(CMAKE_CXX_FLAGS_RELEASE        "/O2 /Oi /Oy /Zc:inline ${CMAKE_CXX_FLAGS_RELEASE}")
  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/O2 /Oi /Zi ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
  set(CMAKE_EXE_LINKER_FLAGS         "/LTCG:INCREMENTAL ${CMAKE_EXE_LINKER_FLAGS}")
else()
  set(CMAKE_CXX_FLAGS                "-Wall -Werror -fPIC -fstrict-enums ${CMAKE_CXX_FLAGS}")
  set(CMAKE_CXX_FLAGS_DEBUG          "-g -Og ${CMAKE_CXX_FLAGS_DEBUG}")
  set(CMAKE_CXX_FLAGS_RELEASE        "-O3 ${CMAKE_CXX_FLAGS_RELEASE}")
  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -g ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

macro(remove_flags)
  foreach(var ${ARGN})
    string(REPLACE "${var}" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
    string(REPLACE "${var}" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
    string(REPLACE "${var}" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
    string(REPLACE "${var}" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
    remove_definitions(${var})
  endforeach()
endmacro(remove_flags)

remove_flags(-DNDEBUG)

list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

if (CYGWIN)
  set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
endif()

find_package(Git REQUIRED)
add_custom_command(
  OUTPUT "${PROJECT_BINARY_DIR}/version_gen.h.tmp"
  COMMAND "${CMAKE_COMMAND}" -E echo_append "#define ALIVE_VERSION_MACRO " > "${PROJECT_BINARY_DIR}/version_gen.h.tmp"
  COMMAND "${GIT_EXECUTABLE}" describe --tags --dirty --always >> "${PROJECT_BINARY_DIR}/version_gen.h.tmp"
  WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
  VERBATIM
)
add_custom_command(
  DEPENDS "${PROJECT_BINARY_DIR}/version_gen.h.tmp"
  OUTPUT "${PROJECT_BINARY_DIR}/version_gen.h"
  COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_BINARY_DIR}/version_gen.h.tmp" "${PROJECT_BINARY_DIR}/version_gen.h"
  COMMAND "${CMAKE_COMMAND}" -E remove -f "${PROJECT_BINARY_DIR}/version_gen.h.tmp"
  WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
  VERBATIM
)
add_custom_target(
  generate_version ALL
  DEPENDS "${PROJECT_BINARY_DIR}/version_gen.h"
  WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
)

find_package(ZLIB)
find_package(Z3 4.8.5 REQUIRED)
include_directories(${Z3_INCLUDE_DIR})

find_program(RE2C re2c)
if (RE2C)
  message(STATUS "RE2C: ${RE2C}")
else()
  message(SEND_ERROR "re2c executable not found")
endif()
add_custom_command(OUTPUT "${PROJECT_BINARY_DIR}/tools/alive_lexer.cpp"
                   COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/tools"
                   COMMAND ${RE2C} ARGS "-d" "-b" "-T" "--no-generation-date"
                   "-o" "${PROJECT_BINARY_DIR}/tools/alive_lexer.cpp"
                   "${PROJECT_SOURCE_DIR}/tools/alive_lexer.re"
                   DEPENDS "tools/alive_lexer.re")

include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_BINARY_DIR})

set(IR_SRCS
  ir/attrs.cpp
  ir/constant.cpp
  ir/fast_math.cpp
  ir/function.cpp
  ir/functions.cpp
  ir/globals.cpp
  ir/instr.cpp
  ir/memory.cpp
  ir/pointer.cpp
  ir/precondition.cpp
  ir/state.cpp
  ir/state_value.cpp
  ir/type.cpp
  ir/value.cpp
  ir/x86_intrinsics.cpp
)

add_library(ir STATIC ${IR_SRCS})

set(SMT_SRCS
  smt/ctx.cpp
  smt/expr.cpp
  smt/exprs.cpp
  smt/smt.cpp
  smt/solver.cpp
)

add_library(smt STATIC ${SMT_SRCS})

set(TOOLS_SRCS
  tools/transform.cpp
)

add_library(tools STATIC ${TOOLS_SRCS})

set(UTIL_SRCS
  "${PROJECT_BINARY_DIR}/version_gen.h"
  util/compiler.cpp
  util/config.cpp
  util/crc.cpp
  util/errors.cpp
  util/file.cpp
  util/random.cpp
  util/sort.cpp
  util/stopwatch.cpp
  util/symexec.cpp
  util/unionfind.cpp
  util/version.cpp
)

if (BUILD_TV)
  set(UTIL_SRCS
    ${UTIL_SRCS}
    util/parallel.cpp
    util/parallel_fifo.cpp
    util/parallel_null.cpp
    util/parallel_unrestricted.cpp
  )
endif()

add_library(util STATIC ${UTIL_SRCS})
add_dependencies(util generate_version)

set(ALIVE_LIBS ir smt tools util)

find_package(hiredis)
if (HIREDIS_LIBRARIES)
  include_directories(${HIREDIS_INCLUDE_DIR})

  set(CACHE_SRCS
    cache/cache.cpp
  )
  add_library(cache STATIC ${CACHE_SRCS})
  set(ALIVE_LIBS cache ${ALIVE_LIBS})
else()
  set(HIREDIS_LIBRARIES $<0:''>)
  add_compile_definitions(NO_REDIS_SUPPORT)
endif()


if (BUILD_LLVM_UTILS OR BUILD_TV)
  find_package(LLVM REQUIRED CONFIG)

  message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
  message(STATUS "CMAKE_PREFIX_PATH: ${CMAKE_PREFIX_PATH}")
  message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

  if (NOT LLVM_ENABLE_RTTI)
    message(FATAL_ERROR "LLVM must be built with '-DLLVM_ENABLE_RTTI=ON'")
  endif()

  list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
  include(AddLLVM)

  include_directories(${LLVM_INCLUDE_DIRS})
  add_definitions(${LLVM_DEFINITIONS})

  set(LLVM_UTIL_SRCS
    "llvm_util/compare.cpp"
    "llvm_util/known_fns.cpp"
    "llvm_util/llvm_optimizer.cpp"
    "llvm_util/llvm2alive.cpp"
    "llvm_util/utils.cpp"
  )

  add_library(llvm_util STATIC ${LLVM_UTIL_SRCS})
  set(ALIVE_LIBS_LLVM llvm_util ${ALIVE_LIBS})

  add_llvm_executable(alive-tv
    "tools/alive-tv.cpp"
  )
  target_compile_options(alive-tv PRIVATE -fexceptions)

  add_llvm_executable(quick-fuzz
    "tools/quick-fuzz.cpp"
  )
  target_compile_options(quick-fuzz PRIVATE -fexceptions)

  add_llvm_executable(alive-exec
    "tools/alive-exec.cpp"
  )
  target_compile_options(alive-exec PRIVATE -fexceptions)

else()
  set(LLVM_UTIL_SRCS "")
endif()

if (BUILD_TV)
  if (CYGWIN)
    message(FATAL_ERROR "LLVM plugins not supported on cygwin")
  endif()
  message(STATUS "Compiling translation validation plugin")
  add_subdirectory(tv)
  set(FOR_ALIVE2_TEST 0)
  configure_file(
    ${PROJECT_SOURCE_DIR}/scripts/opt-alive.sh.in
    ${PROJECT_BINARY_DIR}/opt-alive.sh
    @ONLY
  )
  set(FOR_ALIVE2_TEST 1)
  configure_file(
    ${PROJECT_SOURCE_DIR}/scripts/opt-alive.sh.in
    ${PROJECT_BINARY_DIR}/opt-alive-test.sh
    @ONLY
  )
  unset(FOR_ALIVE2_TEST)
else()
  message(STATUS "Skipping translation validation plugin")
endif()

add_executable(alive
               "tools/alive.cpp"
               "${PROJECT_BINARY_DIR}/tools/alive_lexer.cpp"
               "tools/alive_parser.cpp"
              )
target_link_libraries(alive PRIVATE ${ALIVE_LIBS})

add_executable(alive-jobserver
               "tools/alive-jobserver.cpp"
              )
install(TARGETS alive alive-jobserver)

#add_library(alive2 SHARED ${IR_SRCS} ${SMT_SRCS} ${TOOLS_SRCS} ${UTIL_SRCS} ${LLVM_UTIL_SRCS})

if (BUILD_LLVM_UTILS OR BUILD_TV)
  llvm_map_components_to_libnames(llvm_libs support core irreader bitwriter analysis passes transformutils codegen AllTargetsCodeGens AllTargetsDescs AllTargetsInfos)
#  target_link_libraries(alive2 PRIVATE ${llvm_libs})
  target_link_libraries(alive-tv PRIVATE ${ALIVE_LIBS_LLVM} ${Z3_LIBRARIES} ${HIREDIS_LIBRARIES} ${llvm_libs})
  target_link_libraries(quick-fuzz PRIVATE ${ALIVE_LIBS_LLVM} ${Z3_LIBRARIES} ${HIREDIS_LIBRARIES} ${llvm_libs})
  target_link_libraries(alive-exec PRIVATE ${ALIVE_LIBS_LLVM} ${Z3_LIBRARIES} ${HIREDIS_LIBRARIES} ${llvm_libs})
  install(TARGETS alive-tv quick-fuzz alive-exec)
endif()

target_link_libraries(alive PRIVATE ${Z3_LIBRARIES} ${HIREDIS_LIBRARIES})
#target_link_libraries(alive2 PRIVATE ${Z3_LIBRARIES} ${HIREDIS_LIBRARIES})

if (NOT DEFINED TEST_NTHREADS)
  ProcessorCount(TEST_NTHREADS)
  if (TEST_NTHREADS EQUAL 0)
    set(TEST_NTHREADS 1)
  endif()
endif()
add_custom_target("check"
                  COMMAND "python3"
                          "${PROJECT_SOURCE_DIR}/tests/lit/lit.py"
                          "-s"
                          "${PROJECT_SOURCE_DIR}/tests"
                          "-j${TEST_NTHREADS}"
                  DEPENDS "alive"
                  USES_TERMINAL
                 )

if (BUILD_TV)
  add_dependencies("check" "alive-tv" "quick-fuzz" "tv")
endif()

# EXTERNAL_PROJECTS option that works analogous to LLVM's LLVM_EXTERNAL_PROJECTS option
foreach(EXTERNAL_PROJECT IN LISTS EXTERNAL_PROJECTS)
  canonicalize_tool_name(${EXTERNAL_PROJECT} PROJECT_CANON)
  message(STATUS "Adding external project ${EXTERNAL_PROJECT} at ${EXTERNAL_${PROJECT_CANON}_SOURCE_DIR}")
  add_subdirectory(${EXTERNAL_${PROJECT_CANON}_SOURCE_DIR} ${EXTERNAL_PROJECT})
endforeach()
